`include "float_arith_pkg.svh"


// module fma_shifter(
// 		   // outputs
		   
// 		   // inputs
// 		   input float_arith::fp_cfg_t fp_cfg,
// 		   input float_arith::float32_t a, b, c );

   
module float32_fma(
		   // outputs
		   output float_arith::float32_t o_result,
		   output float_arith::excp_t o_excp,
		   // inputs
		   input float_arith::fp_cfg_t fp_cfg,
		   input float_arith::float32_t a, b, c,
		   input logic is_mul_neg, // these two signals to impl 4 operations
		   input logic is_c_neg );

   import float_arith::*;
   
   logic [47:0] 	      prod;
   logic [23:0] 	      frac_a, frac_b, frac_c;
   logic 		      c_is_subn;
   logic 		      large_lzn;
   
   logic [8:0] 		      diff_base;
   logic 		      diff_base_borrow;

   logic 		      prod_sign, c_sign; // the true sign of prod and c
   logic 		      is_sub;

   assign frac_a = a.exp ? {1'b1, a.sig} : {a.sig, 1'b0}; // shift for multiply
   assign frac_b = b.exp ? {1'b1, b.sig} : {b.sig, 1'b0};
   // assign frac_c = c.exp ? {1'b1, c.sig} : {c.sig, 1'b0};
   assign frac_c = {|c.exp, c.sig};

   
   assign prod = frac_a * frac_b;

   assign large_lzn = ~|prod[47:24]; // num of leading zeros is too large 
   
   assign {diff_base_borrow, diff_base} = c.exp - a.exp - b.exp + 126; //c.exp - ex - 2;

   assign c_is_subn = c.exp == '0;


   assign prod_sign = is_mul_neg ^ a.sign ^ b.sign;	// the actual operation is a*b+c
   assign c_sign = is_c_neg ^ c.sign;

   assign is_sub = prod_sign ^ c_sign; 



   // ================================================================================
   logic 		      a_is_snan, a_is_qnan, a_is_inf, a_is_0,
			      b_is_snan, b_is_qnan, b_is_inf, b_is_0,
			      c_is_snan, c_is_qnan, c_is_inf;

   logic 		      prod_is_inf;
   logic 		      inf_zero;
   logic 		      invalid;

   logic 		      sel_spl_result;
   float32_t special_result;
   
   assign a_is_snan = (&a.exp) && ~a.sig[22] && |a.sig[21:0];
   assign b_is_snan = (&b.exp) && ~b.sig[22] && |b.sig[21:0];
   assign c_is_snan = (&c.exp) && ~c.sig[22] && |c.sig[21:0];
   
   assign a_is_qnan = (&a.exp) && a.sig[22];
   assign b_is_qnan = (&b.exp) && b.sig[22];
   assign c_is_qnan = (&c.exp) && c.sig[22];
   
   assign a_is_inf = (&a.exp) && (~|a.sig);
   assign b_is_inf = (&b.exp) && (~|b.sig);
   assign c_is_inf = (&c.exp) && (~|c.sig);
   
   assign a_is_0 = {a.exp,a.sig} == '0;
   assign b_is_0 = {b.exp,b.sig} == '0;
   
   assign prod_is_inf = (a_is_inf && ~b_is_0) || (b_is_inf && ~a_is_0); // should use when a,b are not NaN
   
   // invalid and should return default_nan
   assign inf_zero = (a_is_inf && b_is_0) || (b_is_inf && a_is_0);
   assign inf_sub_inf = (prod_is_inf && c_is_inf && is_sub);
   
   // // the result is inf, no exceptions
   // assign result_is_inf = (prod_is_inf && ~c_is_inf) || // sign : prod_sign
   // 			  (c_is_inf && ~prod_is_inf) || // sign : c_sign
   // 			  (prod_is_inf && c_is_inf && ~is_sub); // prod_sign or c_sign

   assign invalid = a_is_snan || b_is_snan || c_is_snan || inf_zero || inf_sub_inf;

   assign sel_spl_result = a.exp==8'hff || b.exp==8'hff || c.exp==8'hff; // a,b,c are inf or nan
   
   always_comb
     begin
	if(a_is_snan)
	  special_result = a | 32'h0040_0000;
	else if(b_is_snan)
	  special_result = b | 32'h0040_0000;
	else if(c_is_snan)
	  special_result = c | 32'h0040_0000;
	else if(inf_zero)	// prior to qNaN
	  special_result = float32_default_nan;
	else if(a_is_qnan)
	  special_result = a;
	else if(b_is_qnan)
	  special_result = b;
	else if(c_is_qnan)
	  special_result = c;
	else if(inf_sub_inf)	// use when a,b are not NaN
	  special_result = float32_default_nan;
	else
	  special_result = { (prod_is_inf ? prod_sign : c_sign), 8'hff, 23'h0 };	// no exceptions
     end
   
   // ================================================================================









   
   // ==================== the prod of the frac ====================
   
   logic [4:0] 		      lzn;
   logic [47:0] 	      prod_sft1, prod_sft2;

   // path 1 : not need lzn, not shift c
   fma_shifter_path1 u_fma_shifter_path1( // inputs
					  .prod(prod),
					  .diff_base_borrow(diff_base_borrow),
					  .diff_base(diff_base),
					  .c_is_subn(c_is_subn),
					  // outputs
					  .prod_sft(prod_sft1)
					 );

   // path2 : need lzn
   fma_shifter_path2 u_fma_shifter_path2(
					 .prod(prod),
					 // outputs
					 .prod_sft(prod_sft2),
					 .lzn(lzn)
					 );



   // ==================== the c operand ====================
   
   // shift right c : two phases
   logic [24*4-1:0] 	      frac_c_sft1_ext;
   logic [24*3-1:0] 	      frac_c_sft1;
   logic 		      frac_c_sticky;

   // phase 1 : shift right -diff_base bits
   // assign frac_c_sft1_ext = {frac_c, 72'b0} >> (~diff_base+1);
   //assign frac_c_sft1_ext = {1'b0, frac_c, 71'b0} >> ~diff_base;
   assign frac_c_sft1_ext = {frac_c, 72'b0} >> (~diff_base+{~c_is_subn});

   always_comb
     begin
	if(~diff_base >= 70)	// when c is normal or subnormal
	  frac_c_sticky = |frac_c;
	else
	  frac_c_sticky = |frac_c_sft1_ext[24:0];
     end

   assign frac_c_sft1 = {frac_c_sft1_ext[24*4-1:25], frac_c_sticky};

   // phase 2 : shift left lzn (0 ~ 23) bits
   logic [24*3-1:0]	frac_c_sft2_ext;
   logic [24*2:0] 	frac_c_sft2;
   
   assign frac_c_sft2_ext = frac_c_sft1 << lzn;
   assign frac_c_sft2 = {frac_c_sft2_ext[24*3-1:24], |frac_c_sft2_ext[23:0]};


   // ==================== the exponent ====================

   logic [7+1:0] 		expn1, expn2;

   assign expn1 = {1'b0, c.exp};
   // assign expn2 = ~diff_base[8:0]+1-lzn + c.exp; // -diff_base-lzn+c.exp
   assign expn2 = a.exp + b.exp - 126 - lzn;

   
   logic [48:0] 	prod_sft;
   logic [48:0] 	frac_c_sft;
   logic [7+1:0] 	expn;	// extend 1 bit to check overflow
   logic 		subn_op_subn;
   logic 		op_sel;
   
   assign op_sel = diff_base_borrow && (~diff_base>=lzn) && ~large_lzn;

   // select operand
   assign prod_sft = {(op_sel ? prod_sft2 : prod_sft1), 1'b0};
   assign frac_c_sft = op_sel ? frac_c_sft2 : {frac_c, 24'b0, 1'b0};
   assign expn = sel_spl_result ? 9'hff : (op_sel ? expn2 : expn1);

   assign subn_op_subn = ~prod_sft[48] && ~frac_c_sft[48];

   
   logic [48:0] 	diff1, diff2, 
			diff1_t, diff2_t, 
			diff, diff_t;
   logic 		borrow1, borrow2;

   logic [48:0] 	sum;
   logic 		carry;
   logic 		sum_subn_into_norm;
   logic 		expn_inc1;
   logic 		is_result_exact_0;
   logic 		sign;

   // ==================== calc sum/diff ====================
   // calc the sum/diff : prod +/- c, borrow1 is the borrow/carry of subtraction or addition
   assign {borrow1, diff1} = ({is_sub, prod_sft^{49{is_sub}}} + frac_c_sft) ^ {50{is_sub}};
   // c - prod
   assign {borrow2, diff2} = frac_c_sft - prod_sft;

   assign diff1_t = borrow1 ? {1'b1, diff1[48:2], |diff1[1:0]} : diff1; // for addition: if carry, shift right for 1 bit
   assign diff2_t = sel_spl_result ? {1'b1, special_result.sig, 25'b0} : diff2;

   assign diff = ((~is_sub || borrow2) && ~sel_spl_result) ? diff1_t : diff2_t;
   
   
   assign sum_subn_into_norm = ~is_sub && subn_op_subn && diff1[48];

   assign is_result_exact_0 = ~|diff[48:1]; // ~|diff;	//lsb is not need to check
   
   assign expn_inc1 = !is_sub && borrow1 && ~sel_spl_result; // addition and produce a carry
   
   // assign {carry, sum} = prod_sft + frac_c_sft;
   assign sign = sel_spl_result ? special_result.sign :
		 (is_sub ? (is_result_exact_0 ? (fp_cfg.rounding_mode==RD) : (prod_sign ^ borrow1)) : prod_sign);
   

   logic [5:0] 		diff_lzn; // max value is 47

   // count leading zeros
   always_comb
     unique casex(diff[48:2]) // 0 ~ 47
       47'b1?????????????????????????????????????????????? : diff_lzn = 0;
       47'b01????????????????????????????????????????????? : diff_lzn = 1;
       47'b001???????????????????????????????????????????? : diff_lzn = 2;
       47'b0001??????????????????????????????????????????? : diff_lzn = 3;
       47'b00001?????????????????????????????????????????? : diff_lzn = 4;
       47'b000001????????????????????????????????????????? : diff_lzn = 5;
       47'b0000001???????????????????????????????????????? : diff_lzn = 6;
       47'b00000001??????????????????????????????????????? : diff_lzn = 7;
       47'b000000001?????????????????????????????????????? : diff_lzn = 8;
       47'b0000000001????????????????????????????????????? : diff_lzn = 9;
       47'b00000000001???????????????????????????????????? : diff_lzn = 10;
       47'b000000000001??????????????????????????????????? : diff_lzn = 11;
       47'b0000000000001?????????????????????????????????? : diff_lzn = 12;
       47'b00000000000001????????????????????????????????? : diff_lzn = 13;
       47'b000000000000001???????????????????????????????? : diff_lzn = 14;
       47'b0000000000000001??????????????????????????????? : diff_lzn = 15;
       47'b00000000000000001?????????????????????????????? : diff_lzn = 16;
       47'b000000000000000001????????????????????????????? : diff_lzn = 17;
       47'b0000000000000000001???????????????????????????? : diff_lzn = 18;
       47'b00000000000000000001??????????????????????????? : diff_lzn = 19;
       47'b000000000000000000001?????????????????????????? : diff_lzn = 20;
       47'b0000000000000000000001????????????????????????? : diff_lzn = 21;
       47'b00000000000000000000001???????????????????????? : diff_lzn = 22;
       47'b000000000000000000000001??????????????????????? : diff_lzn = 23;
       47'b0000000000000000000000001?????????????????????? : diff_lzn = 24;
       47'b00000000000000000000000001????????????????????? : diff_lzn = 25;
       47'b000000000000000000000000001???????????????????? : diff_lzn = 26;
       47'b0000000000000000000000000001??????????????????? : diff_lzn = 27;
       47'b00000000000000000000000000001?????????????????? : diff_lzn = 28;
       47'b000000000000000000000000000001????????????????? : diff_lzn = 29;
       47'b0000000000000000000000000000001???????????????? : diff_lzn = 30;
       47'b00000000000000000000000000000001??????????????? : diff_lzn = 31;
       47'b000000000000000000000000000000001?????????????? : diff_lzn = 32;
       47'b0000000000000000000000000000000001????????????? : diff_lzn = 33;
       47'b00000000000000000000000000000000001???????????? : diff_lzn = 34;
       47'b000000000000000000000000000000000001??????????? : diff_lzn = 35;
       47'b0000000000000000000000000000000000001?????????? : diff_lzn = 36;
       47'b00000000000000000000000000000000000001????????? : diff_lzn = 37;
       47'b000000000000000000000000000000000000001???????? : diff_lzn = 38;
       47'b0000000000000000000000000000000000000001??????? : diff_lzn = 39;
       47'b00000000000000000000000000000000000000001?????? : diff_lzn = 40;
       47'b000000000000000000000000000000000000000001????? : diff_lzn = 41;
       47'b0000000000000000000000000000000000000000001???? : diff_lzn = 42;
       47'b00000000000000000000000000000000000000000001??? : diff_lzn = 43;
       47'b000000000000000000000000000000000000000000001?? : diff_lzn = 44;
       47'b0000000000000000000000000000000000000000000001? : diff_lzn = 45;
       47'b00000000000000000000000000000000000000000000001 : diff_lzn = 46;
       47'b00000000000000000000000000000000000000000000000 : diff_lzn = 47;
     endcase // unique case (prod[47:25])


   logic [7+1:0] 	expn_sft;
   
   always_comb
     begin
	if(expn <= diff_lzn || is_result_exact_0)
	  begin
	     diff_t = diff << (expn - (expn>=1)); // if expn is 0, do not shift (subnormal - subnormal)
	     expn_sft = '0;
	  end
	else			// expn >= diff_lzn => expn=0 => diff_lzn must be 0 !!!
	  begin
	     diff_t = diff << diff_lzn;
	     expn_sft = expn - diff_lzn; // should >= 1
	  end
     end


   // ==================== rounding ====================
   logic lsb, round, stikcy;
   
   assign {lsb, round, sticky} = {diff_t[25], diff_t[24], |diff_t[23:0]};

   logic [1:0] round_inc_t, round_inc;
   logic       round_nearest_even;
   logic       round_carry;
   logic [1:0] tmp_bits;

   always_comb
     begin
   	round_nearest_even = 0;
	
   	unique case(fp_cfg.rounding_mode)
   	  RZ: round_inc_t = 2'b00;
   	  RN: begin
   	     round_inc_t = 2'b10;
   	     round_nearest_even = {round, sticky} == 2'b10;
   	  end
   	  RU: round_inc_t = {2{~sign}};
   	  RD: round_inc_t = {2{sign}};
   	endcase // unique case (fp_cfg.rounding_mode)
     end // always_comb

   assign round_inc = round_nearest_even ? {lsb, 1'b0} : round_inc_t;
   assign {round_carry, tmp_bits} = {round, sticky} + round_inc;

   // after round
   logic [23:0] diff_rnd;
   logic 	carry_rnd;
   logic 	expn_inc2;
   logic [7+1:0] expn_rnd;
   
   // assign {carry_rnd, diff_rnd} = diff_t[48:25] + round_carry;
   assign diff_rnd = diff_t[48:25] + round_carry;
   assign carry_rnd = &diff_t[48:25] & round_carry;

   assign expn_inc2 = carry_rnd || (~sel_spl_result && subn_op_subn && diff_rnd[23]);
   
   assign expn_rnd = expn_sft + expn_inc1 + expn_inc2;


   // ==================== result & exception ====================
   float32_t result;
   float32_t overflow_val;

   assign result = {sign, expn_rnd[7:0], diff_rnd[22:0]};
   
   assign overflow_val = (|round_inc_t) ? {sign, 8'hff, 23'h0} : // INF
   			 {sign, 8'hfe, 23'h7fffff};	      // Omega


   logic 	is_tiny;

   assign is_tiny = fp_cfg.tininess_detect ?
   		    (expn_sft==0 && ~sum_subn_into_norm) : // before rounding
   		    (expn_rnd == '0);			   // after rounding


   assign o_excp.invalid = invalid && sel_spl_result;
   assign o_excp.overflow  = (expn_rnd > 9'd254) && ~sel_spl_result;
   assign o_excp.inexact = (round || sticky || o_excp.overflow) && ~sel_spl_result;
   assign o_excp.underflow = (is_tiny && (round || sticky)) && ~sel_spl_result;
   assign o_excp.divbyzero = 1'b0;

   
   assign o_result = o_excp.overflow ? overflow_val : result;
   // assign o_result = special_result;
   
endmodule // float32_fma




// not shift c
module fma_shifter_path1(
			 input logic [47:0] prod,
			 input logic diff_base_borrow,
			 input logic [8:0] diff_base,
			 input logic c_is_subn,
			 output logic [47:0] prod_sft );

   logic [47:0] 			     prod_sft_left, prod_sft_right;
   logic [47:0] 			     tmp_bits;
   logic 				     sticky;

   // shift left (-diff_base) bits
   // assign prod_sft_left = {prod[46:0], 1'b0} << ~diff_base; // shift left : ~diff_base+1-c_is_subn => 
   assign prod_sft_left = prod << (~diff_base + {~c_is_subn});

   // shift right diff_base bits
   assign {prod_sft_right, tmp_bits} = {prod,48'b0} >> (diff_base+c_is_subn);

   always_comb
     begin
	if(diff_base >= 48)
	  sticky = |prod;
	else
	  sticky = |tmp_bits;
     end

   // assign prod_sft = diff_base_borrow ? prod_sft_left : prod_sft_right;
   assign prod_sft = diff_base_borrow ? prod_sft_left : {prod_sft_right[47:1], prod_sft_right[0]|sticky};
   
endmodule // fma_shifter_path1

   
// c also need shift right
module fma_shifter_path2(
			 input logic [47:0] prod,
			 output logic [47:0] prod_sft,
			 output logic [4:0] lzn );

   // count leading zeros
   always_comb
     unique casex(prod[47:25]) // 0 ~ 23
       23'b1?????????????????????? : lzn=0;
       23'b01????????????????????? : lzn=1;
       23'b001???????????????????? : lzn=2;
       23'b0001??????????????????? : lzn=3;
       23'b00001?????????????????? : lzn=4;
       23'b000001????????????????? : lzn=5;
       23'b0000001???????????????? : lzn=6;
       23'b00000001??????????????? : lzn=7;
       23'b000000001?????????????? : lzn=8;
       23'b0000000001????????????? : lzn=9;
       23'b00000000001???????????? : lzn=10;
       23'b000000000001??????????? : lzn=11;
       23'b0000000000001?????????? : lzn=12;
       23'b00000000000001????????? : lzn=13;
       23'b000000000000001???????? : lzn=14;
       23'b0000000000000001??????? : lzn=15;
       23'b00000000000000001?????? : lzn=16;
       23'b000000000000000001????? : lzn=17;
       23'b0000000000000000001???? : lzn=18;
       23'b00000000000000000001??? : lzn=19;
       23'b000000000000000000001?? : lzn=20;
       23'b0000000000000000000001? : lzn=21;
       23'b00000000000000000000001 : lzn=22;
       23'b00000000000000000000000 : lzn=23;
     endcase // unique case (prod[47:25])
	
   assign prod_sft = prod << lzn;

endmodule // fma_shifter_path2
